Análise de texto no R

A quantidade de informações geradas atualmente são incríveis e consequentemente a forma como eles estão estruturados pode variar muito, entre elas, o casos dos textos.

Análise de texto não é algo relativamente novo, podemos notar isso em papers e livros sobre análise de discurso ou conteúdo. Ocorre que o poder computacional foi capaz de trazer análises ainda mais escaláveis do que se fazia antes e com uma força quantitativa muito mais robusta.

No R existem muitos pacotes para lidar com textos, que vão desde contar frequência de palavras até rodar modelos estatísticos. Vamos tentar percorrar estes tópicos utilizando como bases os discursos da Dilma e do Temer.

Estes discursos foram obtidos utilizando webscraping e seu código está disponível aqui. Não recomedo baixá-los novamente, vai demorar uma eternidade!

Revisando estrutura de dados: strings

Ao longo das aulas vimos como podemos guardar variáveis tipo texto no R. Estes objetos devem sempre estar entre aspas, sejam as aspas simples '' ou duplas "".

c("Oi", "Tudo", "bem")
[1] "Oi"   "Tudo" "bem" 

Estes tipos de objetos podem ser manipulados assim como tantos outros objetos, porém não podemos, por exemplo, fazer a média de c("Oi", "Tchau"). Uma das principais função para manipulação de texto no R é o stringr criado por Hadley Wickham. Vamos ver como ele funciona!

stringr

A maior parte das funções do stringr tem como prefixo str_, como por exemplo, str_replace(), str_detect() ou str_extract() e para habilitar ela para uso fazemos,

# install.packages("stringr")
library(stringr)

Vamos usar o seguinte vetor como exemplo:

textos <- c("Programacao em R", "Ciências Sociais", "Sociologia", "Programar é legal", "HaDlEy WiCkHaM", "R")

str_lenght()

O str_lenght() serve para verificarmos o número de caracteres das palavras. Note que ele é diferente da função length() que apenas conta a quantidade de elementos no vetor.

str_length(textos)
[1] 16 16 10 17 14  1
length(textos)
[1] 6

str_c()

O str_c() nos ajuda a concatenar as strings em uma única. Vamos ver um exemplo concatenando os elementos das posições um e dois do nosso vetor “textos”.

str_c(textos[1], textos[2], sep = " para ")
[1] "Programacao em R para Ciências Sociais"

Basicamente, colamos “Programacao em R” e “Ciências Sociais” separado por " para “. Simples, não?

str_to_lower(), str_to_title(), str_to_upper()

Estas funções são bem intuitivas, olha só:

str_to_lower(textos)
[1] "programacao em r"  "ciências sociais"  "sociologia"        "programar é legal" "hadley wickham"    "r"                
str_to_title(textos)
[1] "Programacao Em R"  "Ciências Sociais"  "Sociologia"        "Programar É Legal" "Hadley Wickham"    "R"                
str_to_upper(textos)
[1] "PROGRAMACAO EM R"  "CIÊNCIAS SOCIAIS"  "SOCIOLOGIA"        "PROGRAMAR É LEGAL" "HADLEY WICKHAM"    "R"                

str_replace() e str_replace_all()

A função str_replace() e str_replace_all() localizam um padrão e o substituem por algo de seu interesse, por exemplo, queremos substituir todas as letras r dos elementos do nosso vetor por Python.

str_replace(textos, "R", "Python")
[1] "Programacao em Python" "Ciências Sociais"      "Sociologia"            "Programar é legal"     "HaDlEy WiCkHaM"       
[6] "Python"               

Ops, parece que o r em caixa baixa é diferente do R em caixa alta! Isso significa que estas funções são case-sensitive, ou seja, precisamos especificar que queremos transformar tanto “r” e “R” em Python.

mud_R <- str_replace(textos, "R", "Python")
mud_r <- str_replace(mud_R, "r", "Python")
mud_r
[1] "PPythonogramacao em Python" "Ciências Sociais"           "Sociologia"                 "PPythonogramar é legal"    
[5] "HaDlEy WiCkHaM"             "Python"                    

Parece que ainda não deu certo! O elemento “PPythonogramar é legal” ainda tem “r” que não foram substituídos. Para solucionar esse problema nós utilizamos str_replace_all() que vai substituir todos os padrões do texto.

mud_r
[1] "PPythonogPythonamacao em Python"  "Ciências Sociais"                 "Sociologia"                      
[4] "PPythonogPythonamaPython é legal" "HaDlEy WiCkHaM"                   "Python"                          

str_detect()

O str_detect() irá detectar se existe um padrão específico nos seus textos, como por exemplo, vamos detectar em quais elementos aparecem a palavra programar.

str_detect(textos, "Programar")
[1] FALSE FALSE FALSE  TRUE FALSE FALSE

str_extract()

O str_extract() extraí um padrão dos elementos do texto.

str_extract(textos, "Programar")
[1] NA          NA          NA          "Programar" NA          NA         

Expressões Regulares (Regex)

Existem formas de sinalizarmos alguns padrões nos textos, como por exemplo, falar que queremos o r em caixa baixa ou o R em caixa alta. Estas formas de sinalizar padrões são chamadas de expressões regulares. No wikipedia a definição de regex é a seguinte,

“uma forma concisa e flexível de identificar cadeias de caracteres de interesse, como caracteres particulares, palavras ou padrões de caracteres”

Dê uma pausa e leia o material do Curso R sobre expressões regulares: http://material.curso-r.com/stringr/#express%C3%B5es-regulares

Agora que temos uma noção melhor sobre como manipular textos, vamos analisar os discursos da Dilma e Temer!

Análise dos discurso da Dilma e Temer

Vamos habilitar os pacotes que utilizaremos e abrir os dados com os discursos.

library(tidyverse)
── Attaching packages ──────────────────────────────────────────────────────────────────────────────────────────────── tidyverse 1.2.1 ──
✔ ggplot2 2.2.1.9000     ✔ purrr   0.2.4     
✔ tibble  1.4.2          ✔ dplyr   0.7.4     
✔ tidyr   0.8.0          ✔ stringr 1.3.1     
✔ readr   1.1.1          ✔ forcats 0.3.0     
── Conflicts ─────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
library(lubridate)

Attaching package: ‘lubridate’

The following object is masked from ‘package:base’:

    date
library(quanteda)
library(wordcloud2)
library(stringr)
library(tidytext)
library(SnowballC)
discursos <- read_csv("data/discursos.csv")
Parsed with column specification:
cols(
  date = col_character(),
  title = col_character(),
  discourse = col_character(),
  link_discourse = col_character(),
  who = col_character()
)

O banco de dados tem apenas 5 variáveis, com informações sobre a data, o título do discurso, o link para os discursos, quem os proferiu e o seu cunteúdo. Vamos começar fazendo algumas transoformações básicas.

glimpse(discursos)
Observations: 1,140
Variables: 5
$ date           <chr> "12/05/2016 14h22", "10/05/2016 20h47", "09/05/2016 20h45", "09/05/2016 15h21", "07/05/2016 17h03", "06/05/20...
$ title          <chr> "Declaração à imprensa da Presidenta da República, Dilma Rousseff - Brasília/DF", "Discurso da Presidenta da ...
$ discourse      <chr> "Bom dia. Bom dia senhores e senhoras jornalistas, bom dia - aqui tem parlamentares, ministros, bom dia a tod...
$ link_discourse <chr> "http://www2.planalto.gov.br/acompanhe-o-planalto/discursos/discursos-da-presidenta/declaracao-a-imprensa-da-...
$ who            <chr> "Dilma", "Dilma", "Dilma", "Dilma", "Dilma", "Dilma", "Dilma", "Dilma", "Dilma", "Dilma", "Dilma", "Dilma", "...

A primeira transformação que faremos vai ser transformar a variável date em um objeto interpretado como data e não string, em seguida transformaremos todas as outras variáveis em caixa baixa para termos um padrão nos dados já que como dissemos, as funções para manipulação de texto são case-sensitive. Além disso, com a variável data padronizada de forma correta, vamos criar uma segunda variável para extrair apenas o ano do discurso. Por fim, criaremos uma variável que detecta se o discurso foi ou não proferido no palácio do planalto.

Uma primeira pergunta que podemos responder é: Quantos discursos foram proferidos pelos dois presidentes no Palácio do Planalto?

Podemos notar que ambos presidentes costumam discursar fora do palácio do planalto! Isso é interessante rs.

Pois bem, vamos ao que interessa!

Para este tutorial, utilizaremos dois pacotes principais:

  • quanteda

  • tidytext

Apesar de não serem os pacotes mais “mainstream”, eles permitem fazer o que o pacote tm faz, análises mais robustas com o próprio pacote. Apesar disso, o conceito que percorre estas bibliotecas é muito semelhantes, sobretudo, no quanteda, temos três tipos básicos de objetos:

  1. Corpus

  2. Tokens

  3. Document-feature matrix (DFM)

knitr::include_graphics("img/fluxo_text_minging.png")

Falaremos melhor deles mais a diante, porém antes de avançarmos é bom termos na cabeça como funciona a preparação de um texto para a sua análise.

Preparação do texto

A limpeza do texto, ou até mesmo chamada de “pré-processamento” é um conjunto de práticas voltadas para remover ou alterar partes dos textos que prejudicariam a análise final, como por exemplo, se o texto tiver muitas palavras “tipo”, ele vai influênciar na analise das outras palavras, mesmo que ela não tenha sentido nenhum para o que estamos tratando. Se você ainda não se convenceu, pare para pensar quando um colega vai apresentar um trabalho e fica falando “tipo” toda hora!

Podemos nos perguntar: Existe uma limpeza correta? Não! Tudo depende do interesse do pesquisador, como por exemplo, se você está trabalhando com valores em textos, talvez não seja interessante para você removê-los.

Porém, é bom saber que apesar de não ter uma limpeza melhor do que as outras, elas podem influênciar na análise de estatísticas mais robustas ou até mesmo a frequência das palavras. Se você ficou curiosa(o) para saber mais sobre isso acesse este artigo aqui.

Nós vamos seguir a limpeza proposta pelo artigo acima, inclundo uma limpeza número 0A e 0B, muito mais relacionada com a condição do nosso dado:

0A. Remover não discursos

Prestem atenção no resultado do código abaixo,

discursos %>% top_n(n = 5, discourse) %>% select(discourse)

Muitos deles começam com uma localidade, seguidos de uma sequência de espaços para então chegar no discurso. Além disso, os textos se encerram com um texto escrito “ouça na integra…”. Estas coisas não fazem parte do texto, logo precisaremos removê-los!

Não se preocupe caso não entenda de primeira, “regex” é algo que pegamos o costume com o tempo!

discursos <- discursos %>% mutate(discursos_limpos = str_replace_all(discursos_limpos, ".*[0-9]\\s{4,}"))
Error in mutate_impl(.data, dots) : 
  Evaluation error: argumento "replacement" ausente, sem padrão.
  1. Pontuação

Para remover a pontuação dos textos

discursos %>% 
  mutate(discursos_limpos)
LS0tCnRpdGxlOiAiQXVsYSBUZXh0IE1pbmluZyIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKIyBBbsOhbGlzZSBkZSB0ZXh0byBubyBSCgpBIHF1YW50aWRhZGUgZGUgaW5mb3JtYcOnw7VlcyBnZXJhZGFzIGF0dWFsbWVudGUgc8OjbyBpbmNyw612ZWlzIGUgY29uc2VxdWVudGVtZW50ZSBhIGZvcm1hIGNvbW8gZWxlcyBlc3TDo28gZXN0cnV0dXJhZG9zIHBvZGUgdmFyaWFyIG11aXRvLCBlbnRyZSBlbGFzLCBvIGNhc29zIGRvcyB0ZXh0b3MuCgpBbsOhbGlzZSBkZSB0ZXh0byBuw6NvIMOpIGFsZ28gcmVsYXRpdmFtZW50ZSBub3ZvLCBwb2RlbW9zIG5vdGFyIGlzc28gZW0gX3BhcGVyc18gZSBsaXZyb3Mgc29icmUgYW7DoWxpc2UgZGUgZGlzY3Vyc28gb3UgY29udGXDumRvLiBPY29ycmUgcXVlIG8gcG9kZXIgY29tcHV0YWNpb25hbCBmb2kgY2FwYXogZGUgdHJhemVyIGFuw6FsaXNlcyBhaW5kYSBtYWlzIGVzY2Fsw6F2ZWlzIGRvIHF1ZSBzZSBmYXppYSBhbnRlcyBlIGNvbSB1bWEgZm9yw6dhIHF1YW50aXRhdGl2YSBtdWl0byBtYWlzIHJvYnVzdGEuCgpObyBfX1JfXyBleGlzdGVtIG11aXRvcyBwYWNvdGVzIHBhcmEgbGlkYXIgY29tIHRleHRvcywgcXVlIHbDo28gZGVzZGUgY29udGFyIGZyZXF1w6puY2lhIGRlIHBhbGF2cmFzIGF0w6kgcm9kYXIgbW9kZWxvcyBlc3RhdMOtc3RpY29zLiBWYW1vcyB0ZW50YXIgcGVyY29ycmFyIGVzdGVzIHTDs3BpY29zIHV0aWxpemFuZG8gY29tbyBiYXNlcyBvcyBkaXNjdXJzb3MgZGEgRGlsbWEgZSBkbyBUZW1lci4KCkVzdGVzIGRpc2N1cnNvcyBmb3JhbSBvYnRpZG9zIHV0aWxpemFuZG8gd2Vic2NyYXBpbmcgZSBzZXUgY8OzZGlnbyBlc3TDoSBkaXNwb27DrXZlbCBbYXF1aV0oaHR0cHM6Ly9naXRodWIuY29tL25naWFjaGV0dGEvT2ZpY2luYUNJUy1VU1AvYmxvYi9tYXN0ZXIvV2ViU2NyYXBpbmcuUm1kKS4gTsOjbyByZWNvbWVkbyBiYWl4w6EtbG9zIG5vdmFtZW50ZSwgdmFpIGRlbW9yYXIgdW1hIGV0ZXJuaWRhZGUhCgojIyBSZXZpc2FuZG8gZXN0cnV0dXJhIGRlIGRhZG9zOiBzdHJpbmdzCgpBbyBsb25nbyBkYXMgYXVsYXMgdmltb3MgY29tbyBwb2RlbW9zIGd1YXJkYXIgdmFyacOhdmVpcyB0aXBvIHRleHRvIG5vIFIuIEVzdGVzIG9iamV0b3MgZGV2ZW0gc2VtcHJlIGVzdGFyIGVudHJlIGFzcGFzLCBzZWphbSBhcyBhc3BhcyBzaW1wbGVzIGAnJ2Agb3UgZHVwbGFzIGAiImAuCgpgYGB7cn0KYygiT2kiLCAiVHVkbyIsICJiZW0iKQpgYGAKCkVzdGVzIHRpcG9zIGRlIG9iamV0b3MgcG9kZW0gc2VyIG1hbmlwdWxhZG9zIGFzc2ltIGNvbW8gdGFudG9zIG91dHJvcyBvYmpldG9zLCBwb3LDqW0gbsOjbyBwb2RlbW9zLCBwb3IgZXhlbXBsbywgZmF6ZXIgYSBtw6lkaWEgZGUgYGMoIk9pIiwgIlRjaGF1IilgLiBVbWEgZGFzIHByaW5jaXBhaXMgZnVuw6fDo28gcGFyYSBtYW5pcHVsYcOnw6NvIGRlIHRleHRvIG5vIF9fUl9fIMOpIG8gX19zdHJpbmdyX18gY3JpYWRvIHBvciBIYWRsZXkgV2lja2hhbS4gVmFtb3MgdmVyIGNvbW8gZWxlIGZ1bmNpb25hIQoKIyMgX19zdHJpbmdyX18KCkEgbWFpb3IgcGFydGUgZGFzIGZ1bsOnw7VlcyBkbyBfX3N0cmluZ3JfXyB0ZW0gY29tbyBwcmVmaXhvIGBzdHJfYCwgY29tbyBwb3IgZXhlbXBsbywgYHN0cl9yZXBsYWNlKClgLCBgc3RyX2RldGVjdCgpYCBvdSBgc3RyX2V4dHJhY3QoKWAgZSBwYXJhIGhhYmlsaXRhciBlbGEgcGFyYSB1c28gZmF6ZW1vcywKCmBgYHtyfQojIGluc3RhbGwucGFja2FnZXMoInN0cmluZ3IiKQpsaWJyYXJ5KHN0cmluZ3IpCmBgYAoKVmFtb3MgdXNhciBvIHNlZ3VpbnRlIHZldG9yIGNvbW8gZXhlbXBsbzoKCmBgYHtyfQp0ZXh0b3MgPC0gYygiUHJvZ3JhbWFjYW8gZW0gUiIsICJDacOqbmNpYXMgU29jaWFpcyIsICJTb2Npb2xvZ2lhIiwgIlByb2dyYW1hciDDqSBsZWdhbCIsICJIYURsRXkgV2lDa0hhTSIsICJSIikKYGBgCgojIyMgYHN0cl9sZW5naHQoKWAKCk8gYHN0cl9sZW5naHQoKWAgc2VydmUgcGFyYSB2ZXJpZmljYXJtb3MgbyBuw7ptZXJvIGRlIGNhcmFjdGVyZXMgZGFzIHBhbGF2cmFzLiBOb3RlIHF1ZSBlbGUgw6kgZGlmZXJlbnRlIGRhIGZ1bsOnw6NvIGBsZW5ndGgoKWAgcXVlIGFwZW5hcyBjb250YSBhIHF1YW50aWRhZGUgZGUgZWxlbWVudG9zIG5vIHZldG9yLgoKYGBge3J9CnN0cl9sZW5ndGgodGV4dG9zKQoKbGVuZ3RoKHRleHRvcykKYGBgCgojIyMgYHN0cl9jKClgCgpPIGBzdHJfYygpYCBub3MgYWp1ZGEgYSBjb25jYXRlbmFyIGFzIHN0cmluZ3MgZW0gdW1hIMO6bmljYS4gVmFtb3MgdmVyIHVtIGV4ZW1wbG8gY29uY2F0ZW5hbmRvIG9zIGVsZW1lbnRvcyBkYXMgcG9zacOnw7VlcyB1bSBlIGRvaXMgZG8gbm9zc28gdmV0b3IgInRleHRvcyIuCgpgYGB7cn0Kc3RyX2ModGV4dG9zWzFdLCB0ZXh0b3NbMl0sIHNlcCA9ICIgcGFyYSAiKQpgYGAKCkJhc2ljYW1lbnRlLCBjb2xhbW9zICJQcm9ncmFtYWNhbyBlbSBSIiBlICJDacOqbmNpYXMgU29jaWFpcyIgc2VwYXJhZG8gcG9yICIgcGFyYSAiLiBTaW1wbGVzLCBuw6NvPwoKIyMjIGBzdHJfdG9fbG93ZXIoKWAsIGBzdHJfdG9fdGl0bGUoKWAsIGBzdHJfdG9fdXBwZXIoKWAKCkVzdGFzIGZ1bsOnw7VlcyBzw6NvIGJlbSBpbnR1aXRpdmFzLCBvbGhhIHPDszoKCmBgYHtyfQpzdHJfdG9fbG93ZXIodGV4dG9zKQpgYGAKCmBgYHtyfQpzdHJfdG9fdGl0bGUodGV4dG9zKQpgYGAKCmBgYHtyfQpzdHJfdG9fdXBwZXIodGV4dG9zKQpgYGAKCiMjIyBgc3RyX3JlcGxhY2UoKWAgZSBgc3RyX3JlcGxhY2VfYWxsKClgCgpBIGZ1bsOnw6NvIGBzdHJfcmVwbGFjZSgpYCBlIGBzdHJfcmVwbGFjZV9hbGwoKWAgbG9jYWxpemFtIHVtIHBhZHLDo28gZSBvIHN1YnN0aXR1ZW0gcG9yIGFsZ28gZGUgc2V1IGludGVyZXNzZSwgcG9yIGV4ZW1wbG8sIHF1ZXJlbW9zIHN1YnN0aXR1aXIgdG9kYXMgYXMgbGV0cmFzIHIgZG9zIGVsZW1lbnRvcyBkbyBub3NzbyB2ZXRvciBwb3IgUHl0aG9uLgoKYGBge3J9CnN0cl9yZXBsYWNlKHRleHRvcywgIlIiLCAiUHl0aG9uIikKYGBgCgpPcHMsIHBhcmVjZSBxdWUgbyByIGVtIGNhaXhhIGJhaXhhIMOpIGRpZmVyZW50ZSBkbyBSIGVtIGNhaXhhIGFsdGEhIElzc28gc2lnbmlmaWNhIHF1ZSBlc3RhcyBmdW7Dp8O1ZXMgc8OjbyBjYXNlLXNlbnNpdGl2ZSwgb3Ugc2VqYSwgcHJlY2lzYW1vcyBlc3BlY2lmaWNhciBxdWUgcXVlcmVtb3MgdHJhbnNmb3JtYXIgdGFudG8gInIiIGUgIlIiIGVtIFB5dGhvbi4KCmBgYHtyfQptdWRfUiA8LSBzdHJfcmVwbGFjZSh0ZXh0b3MsICJSIiwgIlB5dGhvbiIpCm11ZF9yIDwtIHN0cl9yZXBsYWNlKG11ZF9SLCAiciIsICJQeXRob24iKQptdWRfcgpgYGAKClBhcmVjZSBxdWUgYWluZGEgbsOjbyBkZXUgY2VydG8hIE8gZWxlbWVudG8gIlBQeXRob25vZ3JhbWFyIMOpIGxlZ2FsIiBhaW5kYSB0ZW0gInIiIHF1ZSBuw6NvIGZvcmFtIHN1YnN0aXR1w61kb3MuIFBhcmEgc29sdWNpb25hciBlc3NlIHByb2JsZW1hIG7Ds3MgdXRpbGl6YW1vcyBgc3RyX3JlcGxhY2VfYWxsKClgIHF1ZSB2YWkgc3Vic3RpdHVpciB0b2RvcyBvcyBwYWRyw7VlcyBkbyB0ZXh0by4KCmBgYHtyfQptdWRfUiA8LSBzdHJfcmVwbGFjZV9hbGwodGV4dG9zLCAiUiIsICJQeXRob24iKQptdWRfciA8LSBzdHJfcmVwbGFjZV9hbGwobXVkX1IsICJyIiwgIlB5dGhvbiIpCm11ZF9yCmBgYAoKIyMjIGBzdHJfZGV0ZWN0KClgCgpPIGBzdHJfZGV0ZWN0KClgIGlyw6EgZGV0ZWN0YXIgc2UgZXhpc3RlIHVtIHBhZHLDo28gZXNwZWPDrWZpY28gbm9zIHNldXMgdGV4dG9zLCBjb21vIHBvciBleGVtcGxvLCB2YW1vcyBkZXRlY3RhciBlbSBxdWFpcyBlbGVtZW50b3MgYXBhcmVjZW0gYSBwYWxhdnJhIHByb2dyYW1hci4KCmBgYHtyfQpzdHJfZGV0ZWN0KHRleHRvcywgIlByb2dyYW1hciIpCmBgYAoKIyMjIGBzdHJfZXh0cmFjdCgpYAoKTyBgc3RyX2V4dHJhY3QoKWAgZXh0cmHDrSB1bSBwYWRyw6NvIGRvcyBlbGVtZW50b3MgZG8gdGV4dG8uCgpgYGB7cn0Kc3RyX2V4dHJhY3QodGV4dG9zLCAiUHJvZ3JhbWFyIikKYGBgCgojIyBFeHByZXNzw7VlcyBSZWd1bGFyZXMgKFJlZ2V4KQoKRXhpc3RlbSBmb3JtYXMgZGUgc2luYWxpemFybW9zIGFsZ3VucyBwYWRyw7VlcyBub3MgdGV4dG9zLCBjb21vIHBvciBleGVtcGxvLCBmYWxhciBxdWUgcXVlcmVtb3MgbyByIGVtIGNhaXhhIGJhaXhhIG91IG8gUiBlbSBjYWl4YSBhbHRhLiBFc3RhcyBmb3JtYXMgZGUgc2luYWxpemFyIHBhZHLDtWVzIHPDo28gY2hhbWFkYXMgZGUgZXhwcmVzc8O1ZXMgcmVndWxhcmVzLiBObyB3aWtpcGVkaWEgYSBkZWZpbmnDp8OjbyBkZSByZWdleCDDqSBhIHNlZ3VpbnRlLAoKPiAidW1hIGZvcm1hIGNvbmNpc2EgZSBmbGV4w612ZWwgZGUgaWRlbnRpZmljYXIgY2FkZWlhcyBkZSBjYXJhY3RlcmVzIGRlIGludGVyZXNzZSwgY29tbyBjYXJhY3RlcmVzIHBhcnRpY3VsYXJlcywgcGFsYXZyYXMgb3UgcGFkcsO1ZXMgZGUgY2FyYWN0ZXJlcyIKCkTDqiB1bWEgcGF1c2EgZSBsZWlhIG8gbWF0ZXJpYWwgZG8gQ3Vyc28gUiBzb2JyZSBleHByZXNzw7VlcyByZWd1bGFyZXM6IGh0dHA6Ly9tYXRlcmlhbC5jdXJzby1yLmNvbS9zdHJpbmdyLyNleHByZXNzJUMzJUI1ZXMtcmVndWxhcmVzCgpBZ29yYSBxdWUgdGVtb3MgdW1hIG5vw6fDo28gbWVsaG9yIHNvYnJlIGNvbW8gbWFuaXB1bGFyIHRleHRvcywgdmFtb3MgYW5hbGlzYXIgb3MgZGlzY3Vyc29zIGRhIERpbG1hIGUgVGVtZXIhCgojIyBBbsOhbGlzZSBkb3MgZGlzY3Vyc28gZGEgRGlsbWEgZSBUZW1lcgoKVmFtb3MgaGFiaWxpdGFyIG9zIHBhY290ZXMgcXVlIHV0aWxpemFyZW1vcyBlIGFicmlyIG9zIGRhZG9zIGNvbSBvcyBkaXNjdXJzb3MuCgpgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkobHVicmlkYXRlKQpsaWJyYXJ5KHF1YW50ZWRhKQpsaWJyYXJ5KHdvcmRjbG91ZDIpCmxpYnJhcnkoc3RyaW5ncikKbGlicmFyeSh0aWR5dGV4dCkKbGlicmFyeShTbm93YmFsbEMpCmBgYAoKYGBge3J9CmRpc2N1cnNvcyA8LSByZWFkX2NzdigiZGF0YS9kaXNjdXJzb3MuY3N2IikKYGBgCgpPIGJhbmNvIGRlIGRhZG9zIHRlbSBhcGVuYXMgNSB2YXJpw6F2ZWlzLCBjb20gaW5mb3JtYcOnw7VlcyBzb2JyZSBhIGRhdGEsIG8gdMOtdHVsbyBkbyBkaXNjdXJzbywgbyBsaW5rIHBhcmEgb3MgZGlzY3Vyc29zLCBxdWVtIG9zIHByb2Zlcml1IGUgbyBzZXUgY3VudGXDumRvLiBWYW1vcyBjb21lw6dhciBmYXplbmRvIGFsZ3VtYXMgdHJhbnNvZm9ybWHDp8O1ZXMgYsOhc2ljYXMuCgpgYGB7cn0KZ2xpbXBzZShkaXNjdXJzb3MpCmBgYAoKQSBwcmltZWlyYSB0cmFuc2Zvcm1hw6fDo28gcXVlIGZhcmVtb3MgdmFpIHNlciB0cmFuc2Zvcm1hciBhIHZhcmnDoXZlbCBgZGF0ZWAgZW0gdW0gb2JqZXRvIGludGVycHJldGFkbyBjb21vIGRhdGEgZSBuw6NvIHN0cmluZywgZW0gc2VndWlkYSB0cmFuc2Zvcm1hcmVtb3MgdG9kYXMgYXMgb3V0cmFzIHZhcmnDoXZlaXMgZW0gY2FpeGEgYmFpeGEgcGFyYSB0ZXJtb3MgdW0gcGFkcsOjbyBub3MgZGFkb3MgasOhIHF1ZSBjb21vIGRpc3NlbW9zLCBhcyBmdW7Dp8O1ZXMgcGFyYSBtYW5pcHVsYcOnw6NvIGRlIHRleHRvIHPDo28gX2Nhc2Utc2Vuc2l0aXZlXy4gQWzDqW0gZGlzc28sIGNvbSBhIHZhcmnDoXZlbCBkYXRhIHBhZHJvbml6YWRhIGRlIGZvcm1hIGNvcnJldGEsIHZhbW9zIGNyaWFyIHVtYSBzZWd1bmRhIHZhcmnDoXZlbCBwYXJhIGV4dHJhaXIgYXBlbmFzIG8gYW5vIGRvIGRpc2N1cnNvLiBQb3IgZmltLCBjcmlhcmVtb3MgdW1hIHZhcmnDoXZlbCBxdWUgZGV0ZWN0YSBzZSBvIGRpc2N1cnNvIGZvaSBvdSBuw6NvIHByb2ZlcmlkbyBubyBwYWzDoWNpbyBkbyBwbGFuYWx0by4KCmBgYHtyfQpkaXNjdXJzb3MgPC0gZGlzY3Vyc29zICU+JSAKICBtdXRhdGUoZGF0ZSA9IGRteV9obShkYXRlKSwKICAgICAgICAgdGl0bGUgPSBzdHJfdG9fbG93ZXIodGl0bGUpLAogICAgICAgICBkaXNjb3Vyc2UgPSBzdHJfdG9fbG93ZXIoZGlzY291cnNlKSwKICAgICAgICAgeWVhciA9IHllYXIoZGF0ZSksCiAgICAgICAgIHBhbGFjaW9fcGxhbmFsdG8gPSBzdHJfZGV0ZWN0KGRpc2NvdXJzZSwgIihecGFsw6FjaW8gZG8gcGxhbmFsdG8pIikpCmBgYAoKVW1hIHByaW1laXJhIHBlcmd1bnRhIHF1ZSBwb2RlbW9zIHJlc3BvbmRlciDDqTogUXVhbnRvcyBkaXNjdXJzb3MgZm9yYW0gcHJvZmVyaWRvcyBwZWxvcyBkb2lzIHByZXNpZGVudGVzIG5vIFBhbMOhY2lvIGRvIFBsYW5hbHRvPwoKYGBge3J9CmRpc2N1cnNvcyAlPiUgCiAgY291bnQocGFsYWNpb19wbGFuYWx0bywgd2hvKSAlPiUgCiAgc3ByZWFkKHBhbGFjaW9fcGxhbmFsdG8sIG4pCmBgYAoKUG9kZW1vcyBub3RhciBxdWUgYW1ib3MgcHJlc2lkZW50ZXMgY29zdHVtYW0gZGlzY3Vyc2FyIGZvcmEgZG8gcGFsw6FjaW8gZG8gcGxhbmFsdG8hIElzc28gw6kgaW50ZXJlc3NhbnRlIHJzLgoKUG9pcyBiZW0sIHZhbW9zIGFvIHF1ZSBpbnRlcmVzc2EhCgpQYXJhIGVzdGUgdHV0b3JpYWwsIHV0aWxpemFyZW1vcyBkb2lzIHBhY290ZXMgcHJpbmNpcGFpczogCgotIGBxdWFudGVkYWAKCi0gYHRpZHl0ZXh0YAoKQXBlc2FyIGRlIG7Do28gc2VyZW0gb3MgcGFjb3RlcyBtYWlzIF8ibWFpbnN0cmVhbSJfLCBlbGVzIHBlcm1pdGVtIGZhemVyIG8gcXVlIG8gcGFjb3RlIGB0bWAgZmF6LCBhbsOhbGlzZXMgbWFpcyByb2J1c3RhcyBjb20gbyBwcsOzcHJpbyBwYWNvdGUuIEFwZXNhciBkaXNzbywgbyBjb25jZWl0byBxdWUgcGVyY29ycmUgZXN0YXMgYmlibGlvdGVjYXMgw6kgbXVpdG8gc2VtZWxoYW50ZXMsIHNvYnJldHVkbywgbm8gYHF1YW50ZWRhYCwgdGVtb3MgdHLDqnMgdGlwb3MgYsOhc2ljb3MgZGUgb2JqZXRvczoKCjEuIENvcnB1cwoKMi4gVG9rZW5zCgozLiBEb2N1bWVudC1mZWF0dXJlIG1hdHJpeCAoREZNKQoKYGBge3J9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJpbWcvZmx1eG9fdGV4dF9taW5naW5nLnBuZyIpCmBgYAoKRmFsYXJlbW9zIG1lbGhvciBkZWxlcyBtYWlzIGEgZGlhbnRlLCBwb3LDqW0gYW50ZXMgZGUgYXZhbsOnYXJtb3Mgw6kgYm9tIHRlcm1vcyBuYSBjYWJlw6dhIGNvbW8gZnVuY2lvbmEgYSBwcmVwYXJhw6fDo28gZGUgdW0gdGV4dG8gcGFyYSBhIHN1YSBhbsOhbGlzZS4KCiMjIyBQcmVwYXJhw6fDo28gZG8gdGV4dG8KCkEgbGltcGV6YSBkbyB0ZXh0bywgb3UgYXTDqSBtZXNtbyBjaGFtYWRhIGRlICJwcsOpLXByb2Nlc3NhbWVudG8iIMOpIHVtIGNvbmp1bnRvIGRlIHByw6F0aWNhcyB2b2x0YWRhcyBwYXJhIHJlbW92ZXIgb3UgYWx0ZXJhciBwYXJ0ZXMgZG9zIHRleHRvcyBxdWUgcHJlanVkaWNhcmlhbSBhIGFuw6FsaXNlIGZpbmFsLCBjb21vIHBvciBleGVtcGxvLCBzZSBvIHRleHRvIHRpdmVyIG11aXRhcyBwYWxhdnJhcyAidGlwbyIsIGVsZSB2YWkgaW5mbHXDqm5jaWFyIG5hIGFuYWxpc2UgZGFzIG91dHJhcyBwYWxhdnJhcywgbWVzbW8gcXVlIGVsYSBuw6NvIHRlbmhhIHNlbnRpZG8gbmVuaHVtIHBhcmEgbyBxdWUgZXN0YW1vcyB0cmF0YW5kby4gU2Ugdm9jw6ogYWluZGEgbsOjbyBzZSBjb252ZW5jZXUsIHBhcmUgcGFyYSBwZW5zYXIgcXVhbmRvIHVtIGNvbGVnYSB2YWkgYXByZXNlbnRhciB1bSB0cmFiYWxobyBlIGZpY2EgZmFsYW5kbyAidGlwbyIgdG9kYSBob3JhIQoKUG9kZW1vcyBub3MgcGVyZ3VudGFyOiBFeGlzdGUgdW1hIGxpbXBlemEgY29ycmV0YT8gTsOjbyEgVHVkbyBkZXBlbmRlIGRvIGludGVyZXNzZSBkbyBwZXNxdWlzYWRvciwgY29tbyBwb3IgZXhlbXBsbywgc2Ugdm9jw6ogZXN0w6EgdHJhYmFsaGFuZG8gY29tIHZhbG9yZXMgZW0gdGV4dG9zLCB0YWx2ZXogbsOjbyBzZWphIGludGVyZXNzYW50ZSBwYXJhIHZvY8OqIHJlbW92w6otbG9zLiAKClBvcsOpbSwgw6kgYm9tIHNhYmVyIHF1ZSBhcGVzYXIgZGUgbsOjbyB0ZXIgdW1hIGxpbXBlemEgbWVsaG9yIGRvIHF1ZSBhcyBvdXRyYXMsIGVsYXMgcG9kZW0gaW5mbHXDqm5jaWFyIG5hIGFuw6FsaXNlIGRlIGVzdGF0w61zdGljYXMgbWFpcyByb2J1c3RhcyBvdSBhdMOpIG1lc21vIGEgZnJlcXXDqm5jaWEgZGFzIHBhbGF2cmFzLiBTZSB2b2PDqiBmaWNvdSBjdXJpb3NhKG8pIHBhcmEgc2FiZXIgbWFpcyBzb2JyZSBpc3NvIGFjZXNzZSBlc3RlIGFydGlnbyBbYXF1aV0oaHR0cDovL3d3dy5ueXUuZWR1L3Byb2plY3RzL3NwaXJsaW5nL2RvY3VtZW50cy9wcmVwcm9jZXNzaW5nLnBkZikuCgpOw7NzIHZhbW9zIHNlZ3VpciBhIGxpbXBlemEgcHJvcG9zdGEgcGVsbyBhcnRpZ28gYWNpbWEsIGluY2x1bmRvIHVtYSBsaW1wZXphIG7Dum1lcm8gMEEgZSAwQiwgbXVpdG8gbWFpcyByZWxhY2lvbmFkYSBjb20gYSBjb25kacOnw6NvIGRvIG5vc3NvIGRhZG86CgowQS4gUmVtb3ZlciBuw6NvIGRpc2N1cnNvcwoKUHJlc3RlbSBhdGVuw6fDo28gbm8gcmVzdWx0YWRvIGRvIGPDs2RpZ28gYWJhaXhvLAoKYGBge3J9CmRpc2N1cnNvcyAlPiUgdG9wX24obiA9IDUsIGRpc2NvdXJzZSkgJT4lIHNlbGVjdChkaXNjb3Vyc2UpCmBgYAoKTXVpdG9zIGRlbGVzIGNvbWXDp2FtIGNvbSB1bWEgbG9jYWxpZGFkZSwgc2VndWlkb3MgZGUgdW1hIHNlcXXDqm5jaWEgZGUgZXNwYcOnb3MgcGFyYSBlbnTDo28gY2hlZ2FyIG5vIGRpc2N1cnNvLiBBbMOpbSBkaXNzbywgb3MgdGV4dG9zIHNlIGVuY2VycmFtIGNvbSB1bSB0ZXh0byBlc2NyaXRvICJvdcOnYSBuYSBpbnRlZ3JhLi4uIi4gRXN0YXMgY29pc2FzIG7Do28gZmF6ZW0gcGFydGUgZG8gdGV4dG8sIGxvZ28gcHJlY2lzYXJlbW9zIHJlbW92w6otbG9zIQoKTsOjbyBzZSBwcmVvY3VwZSBjYXNvIG7Do28gZW50ZW5kYSBkZSBwcmltZWlyYSwgInJlZ2V4IiDDqSBhbGdvIHF1ZSBwZWdhbW9zIG8gY29zdHVtZSBjb20gbyB0ZW1wbyEKCmBgYHtyfQpkaXNjdXJzb3MgPC0gZGlzY3Vyc29zICU+JSBtdXRhdGUoZGlzY3Vyc29zX2xpbXBvcyA9IHN0cl9yZXBsYWNlX2FsbChkaXNjb3Vyc2UsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiKG91w6dhIGEgw61udGVncmEuKikuKnxcXG58KGRlc2Vudm9sdmlkbyBjb20gbyBjbXMpLip8KHBsb25lKSIsICIiKSAlPiUgc3RyX3RyaW0oKSkKZGlzY3Vyc29zIDwtIGRpc2N1cnNvcyAlPiUgbXV0YXRlKGRpc2N1cnNvc19saW1wb3MgPSBzdHJfcmVwbGFjZV9hbGwoZGlzY3Vyc29zX2xpbXBvcywgIi4qWzAtOV1cXHN7NCx9IiwgIiIpKQpgYGAKCgoxLiBQb250dWHDp8OjbwoKUGFyYSByZW1vdmVyIGEgcG9udHVhw6fDo28gZG9zIHRleHRvcwpgYGB7cn0KZGlzY3Vyc29zICU+JSAKICBtdXRhdGUoZGlzY3Vyc29zX2xpbXBvcykKYGBgCgoK